{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "5b1a17c6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1. 图像读取与预处理...\n",
      "2. 生字表特殊预处理...\n",
      "3. 提取方格区域...\n",
      "4. 处理表格线...\n",
      "5. 汉字区域增强...\n",
      "6. 定位汉字方格...\n",
      "7. 排序汉字方格...\n",
      "8. 正在提取 89 个汉字...\n",
      "处理完成！共提取 89 个汉字\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import cv2\n",
    "import numpy as np\n",
    "\n",
    "def extract_chars_from_worksheet(image_path, output_dir='chars'):\n",
    "    \"\"\" 专门用于生字表汉字提取的函数 \"\"\"\n",
    "    \n",
    "    # 创建输出目录\n",
    "    os.makedirs(output_dir, exist_ok=True)\n",
    "    \n",
    "    # 1. 图像读取与预处理\n",
    "    print(\"1. 图像读取与预处理...\")\n",
    "    img = cv2.imread(image_path)\n",
    "    if img is None:\n",
    "        raise FileNotFoundError(f\"无法读取图像文件：{image_path}\")\n",
    "    \n",
    "    # 尺寸标准化（保持宽高比）\n",
    "    height, width = img.shape[:2]\n",
    "    if max(height, width) > 1500:\n",
    "        scale = 1500 / max(height, width)\n",
    "        img = cv2.resize(img, None, fx=scale, fy=scale)\n",
    "    \n",
    "    # 2. 针对生字表的特殊预处理\n",
    "    print(\"2. 生字表特殊预处理...\")\n",
    "    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "    \n",
    "    # 强化方格和汉字对比度\n",
    "    blurred = cv2.GaussianBlur(gray, (5,5), 0)  # 保持模糊核大小\n",
    "    clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))\n",
    "    enhanced = clahe.apply(blurred)\n",
    "    \n",
    "    # 3. 提取方格区域\n",
    "    print(\"3. 提取方格区域...\")\n",
    "    binary = cv2.adaptiveThreshold(enhanced, 255, \n",
    "                                 cv2.ADAPTIVE_THRESH_GAUSSIAN_C, \n",
    "                                 cv2.THRESH_BINARY_INV, 45, 15)  # 保持阈值参数\n",
    "    \n",
    "    # 4. 检测并去除表格线\n",
    "    print(\"4. 处理表格线...\")\n",
    "    # 水平线检测\n",
    "    horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (50,1))  # 保持水平线检测核大小\n",
    "    horizontal = cv2.morphologyEx(binary, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)\n",
    "    \n",
    "    # 垂直线检测\n",
    "    vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,50))  # 保持垂直线检测核大小\n",
    "    vertical = cv2.morphologyEx(binary, cv2.MORPH_OPEN, vertical_kernel, iterations=2)\n",
    "    \n",
    "    # 合并表格线并去除\n",
    "    table_lines = cv2.add(horizontal, vertical)\n",
    "    no_lines = cv2.subtract(binary, table_lines)\n",
    "    \n",
    "    # 5. 汉字区域增强\n",
    "    print(\"5. 汉字区域增强...\")\n",
    "    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))  # 保持形态学操作的核大小\n",
    "    enhanced_chars = cv2.morphologyEx(no_lines, cv2.MORPH_CLOSE, kernel, iterations=2)\n",
    "    \n",
    "    # 6. 定位汉字方格\n",
    "    print(\"6. 定位汉字方格...\")\n",
    "    contours, _ = cv2.findContours(enhanced_chars, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n",
    "    \n",
    "    # 过滤条件\n",
    "    min_area = 1000  # 保持最小面积阈值\n",
    "    max_area = 12000  # 调整最大面积阈值\n",
    "    valid_cells = []\n",
    "    \n",
    "    for cnt in contours:\n",
    "        x,y,w,h = cv2.boundingRect(cnt)\n",
    "        area = w * h\n",
    "        \n",
    "        # 只保留符合方格特征的区域\n",
    "        if min_area < area < max_area and 0.7 < w/h < 1.3:  # 放宽松高比范围\n",
    "            valid_cells.append((x,y,w,h))\n",
    "    \n",
    "    # 7. 按行列排序方格\n",
    "    print(\"7. 排序汉字方格...\")\n",
    "    if valid_cells:\n",
    "        # 计算平均行高和列宽\n",
    "        avg_height = np.mean([h for _,_,_,h in valid_cells])\n",
    "        avg_width = np.mean([w for _,_,w,_ in valid_cells])\n",
    "        \n",
    "        # 按行和列排序\n",
    "        valid_cells.sort(key=lambda c: (c[1]//int(avg_height*0.9), c[0]//int(avg_width*0.9)))\n",
    "    \n",
    "    # 8. 提取每个方格中的汉字\n",
    "    print(f\"8. 正在提取 {len(valid_cells)} 个汉字...\")\n",
    "    for i, (x,y,w,h) in enumerate(valid_cells):\n",
    "        # 从原始图像提取方格区域（扩大10%范围）\n",
    "        pad = int(min(w,h)*0.1)\n",
    "        roi = enhanced[max(0,y-pad):min(height,y+h+pad), \n",
    "                      max(0,x-pad):min(width,x+w+pad)]\n",
    "        \n",
    "        # 对单个方格进行二值化\n",
    "        _, cell_binary = cv2.threshold(roi, 0, 255, \n",
    "                                      cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)\n",
    "        \n",
    "        # 提取最大连通区域（去除小噪点）\n",
    "        num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(cell_binary)\n",
    "        if num_labels > 1:\n",
    "            # 找到第二大的区域（第一大为背景）\n",
    "            sizes = stats[1:, -1]\n",
    "            max_idx = np.argmax(sizes) + 1\n",
    "            char_img = np.uint8(labels == max_idx) * 255\n",
    "            \n",
    "            # 裁剪到字符实际区域\n",
    "            x2,y2,w2,h2,_ = stats[max_idx]\n",
    "            char_img = char_img[y2:y2+h2, x2:x2+w2]\n",
    "            \n",
    "            # 标准化输出\n",
    "            char_img = cv2.copyMakeBorder(char_img, 20,20,20,20, \n",
    "                                        cv2.BORDER_CONSTANT, value=0)\n",
    "            char_img = cv2.resize(char_img, (100,100), \n",
    "                                interpolation=cv2.INTER_AREA)\n",
    "            \n",
    "            # 保存汉字\n",
    "            cv2.imwrite(f\"{output_dir}/hanzi_{i+1:03d}.png\", char_img)\n",
    "    \n",
    "    print(f\"处理完成！共提取 {len(valid_cells)} 个汉字\")\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    input_image = \"hanzi1.jpg\"\n",
    "    \n",
    "    if not os.path.exists(input_image):\n",
    "        print(f\"错误：输入图像 {input_image} 不存在\")\n",
    "    else:\n",
    "        try:\n",
    "            extract_chars_from_worksheet(input_image)\n",
    "        except Exception as e:\n",
    "            print(f\"处理过程中发生错误：{str(e)}\")\n",
    "\n",
    "\n",
    "\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
